package de.ttf.ts.ctx;

import static org.apache.commons.lang.exception.ExceptionUtils.getThrowables;
import static org.apache.commons.lang.exception.ExceptionUtils.indexOfType;

import java.net.InetAddress;

import javax.persistence.OptimisticLockException;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hibernate.exception.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ttf.ts.control.exception.ErrorCode;
import de.ttf.ts.control.exception.ServiceBusinessException;
import de.ttf.ts.control.exception.ServiceException;

/**
 * Interceptor zum Abfangen technischer Exceptions, die nicht in der Anwendung
 * behandelt wurden. Es werden Hostname und ausgeführte Aktion ermittelt und an
 * den Client zurückgegeben.
 */
public class ExceptionInterceptor implements MethodInterceptor {

    private static final Logger log = LoggerFactory.getLogger(ExceptionInterceptor.class);
    private static final String hostname;
    static {
        String host;
        try {
            host = InetAddress.getLocalHost().getHostName();
        } catch (Throwable t) {
            host = "Unknown";
        }
        hostname = host;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            return invocation.proceed();
        } catch (ServiceBusinessException e) {
            // Fachlicher Fehler
            throw handleFehler(invocation, e);
        } catch (ServiceException e) {
            // Technischer Fehler
            throw handleFehler(invocation, e);
        } catch (Throwable t) {
            ServiceException serviceException = throwableOfType(t, ServiceException.class);
            ConstraintViolationException constraintViolation = throwableOfType(t, ConstraintViolationException.class);
            if (serviceException != null) {
                log.debug("Found ServiceException in causes", t);
                throw handleFehler(invocation, serviceException);
            } else if (throwableOfType(t, OptimisticLockException.class) != null) {
                // Fachlicher Fehler, veraltete Daten beim Update
                throw handleFehler(invocation, new ServiceBusinessException(t, ErrorCode.OPTIMISTIC_LOCK_ERROR));
            } else if (constraintViolation != null) {
                String constraint = constraintViolation.getConstraintName();
                throw handleFehler(invocation, new ServiceBusinessException(t, ErrorCode.DATEN_FEHLER, constraint));
            } else {
                // Unbekannter technischer Fehler
                throw handleFehler(invocation, new ServiceException(t));
            }
        }
    }

    private <T extends Throwable> T throwableOfType(Throwable t, Class<T> type) {
        int index = indexOfType(t, type);
        return index > -1 ? type.cast(getThrowables(t)[index]) : null;
    }

    private Throwable handleFehler(MethodInvocation invocation, ServiceException e) {
        if (e instanceof ServiceBusinessException) {
            return handleFehler(invocation, (ServiceBusinessException) e);
        }
        setHostAndAktion(e, invocation);
        log.error(null, e);
        return e;
    }

    private Throwable handleFehler(MethodInvocation invocation, ServiceBusinessException e) {
        setHostAndAktion(e, invocation);
        log.info(null, e);
        return e;
    }

    private void setHostAndAktion(ServiceException e, MethodInvocation invocation) {
        String aktion = "Unknown";
        if (invocation.getMethod() != null) {
            aktion = invocation.getMethod().getName();
        }
        e.setHostAndAktion(hostname, aktion);
    }

}
